﻿package src {
	
	import flash.display.*;
	import flash.text.*;
	
	/**
	 * A class that represents sequence motif
	 * @author 
	 */
	public class Motif {
		
		/**
		 * Motif name
		 */
		private var name:String = "";
		
		/**
		 * Frequency matrix
		 */
		private var pfm:Array = new Array(4); // frequency matrix
		
		/**
		 * Weight matrix
		 */
		private var pwm:Array = new Array(4); // weight matrix
		
		/**
		 * Probability matrix
		 */
		private var probabilityMatrix:Array = new Array(4); // probability matrix
		
		/**
		 * Number of sequences counted
		 */
		private var numberOfSequences:int = 0;
		
		/**
		 * Motif length, number of positions
		 */
		private var numberOfPositions:int = 0; // sequences length
		
		/**
		 * Motif constructor, inits the data structure
		 * @param	mxFile	-	.mx file as string
		 */
		public function Motif(mxFile:String = ""):void {
			//trace("# Motif object created");
			if (mxFile == "" || mxFile == null) return;
			//trace(mxFile);
			
			var mxFileRows:Array = mxFile.split("\n");
			
			name = String(mxFileRows[0]).substring(1);
			
			// to help writing loops:
			var newRow:Array;
			var oldRow:Array;
			
			// Get the pfm:
			for (var i:int = 1 ; i < 5 ; i++) {
				newRow = String(mxFileRows[i]).split("\t");
				newRow.shift();
				pfm[i - 1] = newRow;
			}
			
			numberOfPositions = pfm[0].length; // TODO - data file error check
			//trace("numberOfPositions=" + numberOfPositions);
			
			// Calculate number of sequences: (TODO - data file error check)
			for (var j:int = 0 ; j < pfm.length ; j++) {
				numberOfSequences += int(pfm[j][0]);
			}
			//trace("numberOfSequences=" + numberOfSequences);
			
			// Calculate the probability matrix:
			for (var m:int = 0 ; m < 4 ; m++) {
				oldRow = pfm[m];
				newRow = new Array(oldRow.length);
				for (var n:int = 0 ; n < oldRow.length ; n++) {
					newRow[n] = oldRow[n] / numberOfSequences;
				}
				probabilityMatrix[m] = newRow;
			}
			
			// Calculate the pwm:
			for (var k:int = 0 ; k < 4; k++) {
				oldRow = pfm[k];
				newRow = new Array(oldRow.length);
				for (var l:int = 0 ; l < oldRow.length ; l++) {
					var probability:Number = oldRow[l] / numberOfSequences;
					if (probability == 0) newRow[l] = 0;
					else newRow[l] = Math.abs(probability * (Math.log(probability) / Math.log(2)));
				}
				pwm[k] = newRow;
			}
			
		}
		
		/**
		 * Draw the logo
		 * @return	-	a Sprite with logo
		 */
		public function drawLogo():Sprite {
			// TODO - draw backround with code and allow different sizes
			// maybe different methods for different sizes?
			
			var sprite = new LogoBckrnd();
			var barWidth:Number = 500/numberOfPositions; // TODO do not hardcode 500
			var barMaxHeight:Number = 200; // TODO do not hardcode 200
			
			var positions:Sprite = new Sprite();
			for(var n:int = 0 ; n<numberOfPositions ; n++) {
				var textField:TextField = new TextField();
				textField.x = n*barWidth+barWidth/2-5;
				textField.selectable = false;
				textField.text = String(n+1);
				positions.addChild(textField);
			}
			sprite.addChild(positions);
			
			var logo:Sprite = new Sprite();
			var letters:Array;
			for(var i:int = 0 ; i<numberOfPositions ; i++) {
				var H:Number = 0;
				for (var j:int = 0 ; j < 4 ; j++) H += Number(pwm[j][i]);
				
				letters = new Array(4);
				for (var k:int = 0 ; k < 4 ; k++) {
					var letter:MovieClip;
					var letterHeight:Number = ((2 - H) * barMaxHeight / 2) * probabilityMatrix[k][i];
					switch(k) {
						case 0: letter = drawA(i * barWidth, 0, int(letterHeight), barWidth); break;
						case 1: letter = drawC(i * barWidth, 0, int(letterHeight), barWidth); break;
						case 2: letter = drawG(i * barWidth, 0, int(letterHeight), barWidth); break;
						case 3: letter = drawT(i * barWidth, 0, int(letterHeight), barWidth); break;
						default: break;
					}
					letters[k] = letter;
				}
				
				var letterIndexes:Array = letters.sortOn("height", Array.RETURNINDEXEDARRAY | Array.NUMERIC);
				var _y:Number = 0;
				for (var l:int = 0 ; l < letterIndexes.length ; l++) {
					letters[letterIndexes[l]].y = _y;
					logo.addChild(letters[letterIndexes[l]]);
					_y -= letters[letterIndexes[l]].height;
				}
			}
			sprite.addChild(logo);
			
			return sprite;
		}
		
		/**
		 * Draw nucleotide A
		 * @param	x
		 * @param	y
		 * @param	height
		 * @param	width
		 * @return
		 */
		private function drawA(x:Number, y:Number, height:Number, width:Number):MovieClip {
			var clip:MovieClip = new LogoSymbol(); // LogoSymbol is from .fla's library
			clip.gotoAndStop(1);
			clip.x = x;
			clip.y = y;
			clip.height = height;
			clip.width = width;
			return clip;
		}
		
		/**
		 * Draw nucleotide C
		 * @param	x
		 * @param	y
		 * @param	height
		 * @param	width
		 * @return
		 */
		private function drawC(x:Number, y:Number, height:Number, width:Number):MovieClip {
			var clip:MovieClip = new LogoSymbol(); // LogoSymbol is from .fla's library
			clip.gotoAndStop(2);
			clip.x = x;
			clip.y = y;
			clip.height = height;
			clip.width = width;
			return clip;
		}
		
		/**
		 * Draw nucleotide G
		 * @param	x
		 * @param	y
		 * @param	height
		 * @param	width
		 * @return
		 */
		private function drawG(x:Number, y:Number, height:Number, width:Number):MovieClip {
			var clip:MovieClip = new LogoSymbol(); // LogoSymbol is from .fla's library
			clip.gotoAndStop(3);
			clip.x = x;
			clip.y = y;
			clip.height = height;
			clip.width = width;
			return clip;
		}
		
		/**
		 * Draw nucleotide T
		 * @param	x
		 * @param	y
		 * @param	height
		 * @param	width
		 * @return
		 */
		private function drawT(x:Number, y:Number, height:Number, width:Number):MovieClip {
			var clip:MovieClip = new LogoSymbol(); // LogoSymbol is from .fla's library
			clip.gotoAndStop(4);
			clip.x = x;
			clip.y = y;
			clip.height = height;
			clip.width = width;
			return clip;
		}
		
		/**
		 * Get motif name
		 * @return
		 */
		public function getName():String {
			return name;
		}
		
	}
	
}